﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Upreader.Usenet.Nntp.Encodings
{
    /// <summary>
    /// A rawStream for nntp messages double dots at the beginning of a new line
    /// </summary>
    class NntpStream : Stream
    {
        #region Configuration

        private const byte Dot = 46;
        private const byte CarriageReturn = 13;
        private const byte NewLineFeed = 10;
        private const bool DefaultIsNewFixedLine = true;
        private const int InternalBufferSize = 1024;

        #endregion // Configuration

        #region Fields

        private readonly Stream _baseStream;

        private LastCharacterState _lastCharacterState;

        private byte[] _internalBuffer;

        #endregion // Fields

        #region Constructor

        public NntpStream(Stream baseStream)
        {
            if (baseStream == null)
                throw new ArgumentNullException("baseStream");

            _baseStream = baseStream;

            _internalBuffer = new byte[0];
            _lastCharacterState = LastCharacterState.NewLineAfterCarriageReturn;
        }

        #endregion // Constructor

        #region Properties

        public Stream BaseStream
        {
            get { return _baseStream; }
        }

        /// <summary>
        /// Always true
        /// </summary>
        public override bool CanRead
        {
            get { return true; }
        }

        /// <summary>
        /// Always false
        /// </summary>
        public override bool CanSeek
        {
            get { return false; }
        }

        /// <summary>
        /// This rawStream is intended for reading only, always false
        /// </summary>
        public override bool CanWrite
        {
            get { return false; }
        }

        public override void Flush()
        {
            _baseStream.Flush();
        }

        /// <summary>
        /// Not supported
        /// </summary>
        public override long Length
        {
            get { throw new NotSupportedException(); }
        }

        /// <summary>
        /// Not supported
        /// </summary>
        public override long Position
        {
            get
            {
                throw new NotSupportedException();
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        /// <summary>
        /// Get whether the current stream is at its end
        /// </summary>
        public bool EndOfStream
        {
            get;
            private set;
        }

        #endregion // Properties

        #region Methods

        public override int Read(byte[] buffer, int offset, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (count < 0)
                throw new ArgumentOutOfRangeException("count");
            if (count > buffer.Length - offset)
                throw new ArgumentOutOfRangeException("count", "to many bytes were requested for the current buffer when starting from offset");

            if (EndOfStream)
                return 0;

            int total = 0;

            // prepare a buffer for reading
            PrepareBuffer(count);
            
            // read the next part in the buffer
            int currentByteCount = _baseStream.Read(_internalBuffer, 0, count);

            for (int currentIndex = 0; currentIndex < currentByteCount; currentIndex++)
            {
                byte currentByte = _internalBuffer[currentIndex];
                bool shouldWrite = true;

                switch (_lastCharacterState)
                {
                    case LastCharacterState.CarriageReturnAfterFirstColumnDot:
                        // the end of the packet is always indicated with a \r\n.\r\n
                        if (currentByte == NewLineFeed)
                        {
                            shouldWrite = false;
                            total -= 2; // compensate for the first column dot and carriage return flag
                            EndOfStream = true;
                        }
                        else
                            _lastCharacterState = LastCharacterState.Unrelevant;
                        break;
                    case LastCharacterState.FirstColumnDot:
                        // if the current character is the second dot in the second column, remove this dot
                        if (currentByte == Dot)
                        {
                            shouldWrite = false;
                            _lastCharacterState = LastCharacterState.Unrelevant;
                        }
                        else if (currentByte == CarriageReturn)
                        {
                            _lastCharacterState = LastCharacterState.CarriageReturnAfterFirstColumnDot;
                        }
                        else
                        {
                            // this dot is not relevant anymore since only the first double dot should be removed
                            _lastCharacterState = LastCharacterState.Unrelevant;
                        }
                        break;
                    case LastCharacterState.Unrelevant:
                        if (currentByte == CarriageReturn)
                            _lastCharacterState = LastCharacterState.CarriageReturn;
                        break;
                    case LastCharacterState.CarriageReturn:
                        if (currentByte == NewLineFeed)
                            _lastCharacterState = LastCharacterState.NewLineAfterCarriageReturn;
                        else if (currentByte != CarriageReturn)
                            _lastCharacterState = LastCharacterState.Unrelevant; // dont forget to reset the a unrelevant character state except when we have patterns like "\r\r\n"
                        break;
                    case LastCharacterState.NewLineAfterCarriageReturn:
                        if (currentByte == Dot)
                            _lastCharacterState = LastCharacterState.FirstColumnDot;
                        else if (currentByte == CarriageReturn)
                            _lastCharacterState = LastCharacterState.CarriageReturn;
                        else
                            _lastCharacterState = LastCharacterState.Unrelevant; 
                        break;
                }

                if (shouldWrite)
                {
                    buffer[offset + total] = currentByte;

                    // were 1 byte futher
                    total++;
                }

                if (EndOfStream)
                {
                    break;
                }
            }
            
            return total;
        }

        /// <summary>
        /// Not supported
        /// </summary>
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Not supported
        /// </summary>
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Not supported
        /// </summary>
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (count < 0)
                throw new ArgumentOutOfRangeException("count");
            if (count > buffer.Length - offset)
                throw new ArgumentOutOfRangeException("count", "to many bytes were requested for the current buffer when starting from offset");

            int endOffset = offset + count;

            throw new NotSupportedException();
        }

        #endregion // Methods

        #region Disposable

        #region Helpers

        private void PrepareBuffer(int count)
        {
            if (count > 0)
            {
                if (_internalBuffer.Length < count)
                {
                    byte[] newArray = new byte[count];
                    _internalBuffer = newArray;
                }
            }
        }

        #endregion // Helpers

        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing)
                {
                    BaseStream.Dispose();
                }
            }
            finally
            {
                base.Dispose(disposing);
            }
        }

        #endregion // Disposable
    }
}
